# Import a common exception type from the urllib2 library.
from urllib2 import HTTPError

# Import the shared data structure used to do device to session mappings.
from SharedMappingList import SharedMappingList

# Import the device interface so that we can communicate with it.
from HSVODeviceInterface import HSVODeviceInterface, OperationNotSupported, OperationFailed

# Import the savoir interface so that we can communicate with it.
from HSVOSavoirInterface import HSVOSavoirInterface

# Import the HSVO message parsing class.
from HSVOMessageReader import HSVOMessageReader

# Import some deployment-specific constants.
from hsvo.constants import SERVICE_ID, BI_MULTISESSION, BI_DEBUG

class HSVOMessageListener():
    """
    HSVOMessageListener()
    
    This class is bound to the STOMP listening thread, and implements various
    callback functions. These functions are triggered when the appropriate event
    occurs in the STOMP library.
    """
    
    def __init__(self):
        self.messageReader = HSVOMessageReader()
        self.mappingList = SharedMappingList()
        self.device = HSVODeviceInterface()
        self.savoir = HSVOSavoirInterface()
    
    def on_connecting(self, host_and_ports):
        """
        on_connecting(host_and_ports) - This method is called when the STOMP 
        client is trying to connect. 
        """
        
        print 'Starting ActiveMQ connection to %s:%s...' % host_and_ports
    
    def on_connected(self, headers, message):
        """
        on_connected(headers, message) - This method is called when the STOMP 
        client has established its connection. 
        """
        
        print 'ActiveMQ connection successful.'
    
    def on_disconnected(self, headers, message):
        """
        on_disconnected(headers, message) - This method is called when the STOMP 
        client has lost its connection. 
        """
        
        print 'Lost connection to JMS queue: %s' % message
    
    def on_error(self, headers, message):
        """
        on_error(headers, message) - This method is called when the STOMP 
        client encounters a network error. 
        """
        
        print 'Received an error: %s' % message
    
    def on_message(self, headers, message):
        """
        on_message(headers, message) - This method is called when the STOMP 
        client has received a message. The labour is divided by HSVO action types.
        """
        
        # Parse and store the received XML message, and figure out which action is being taken.
        self.messageReader.load(message)
        action = self.messageReader.getAction()
        
        # If debugging is enabled, write the received message to the terminal.
        if BI_DEBUG:
            print '\nReceived a "%s" message from SAVOIR...' % (action,)
            print '==== SAVOIR Message ====================================\n'
            print self.messageReader.getMessage().toxml()+'\n'
        
        # Get some general data from the message that we'll need for all action types.
        savoir_sessionID = self.messageReader.getSessionID()
        savoirUser = self.messageReader.getSavoirUser()
        
        if savoirUser == None:
            print "Could not get a savoirUser on the received message."
            return
            
        if action != 'authenticate':
            # If we're not loading a new session, fetch the
            # pre-existing session using the mapping list.
            try:
                device_sessionID = self.mappingList.getMappingBySavoirData(savoir_sessionID, savoirUser).device_sessionID
            except AttributeError:
                print "An unauthenticated message has been received... ignoring."
                return
        
        if action == 'authenticate':
            # Get the username/password from the savoir message.
            authParameters = self.messageReader.getParameters()
            
            try:
                # If this is a multi-session edge device accept a token to use
                # as a session ID. Otherwise, use the SERVICE_ID tag as its token.
                if BI_MULTISESSION:
                    device_sessionID = self.device.authenticate(authParameters)
                    
                    # Save the state of the login so that we can send an authentication reply.
                    loginSuccess = "true"
                    
                    # If debugging is enabled, write the event and its data to the terminal.
                    if BI_DEBUG:
                        print 'Login succeeded! New session ID: %s.' % (device_sessionID,)
                        print 'Adding (%s, %s, %s) to the shared mapping list.' % (savoirUser, savoir_sessionID, device_sessionID)
                
                    # Add a new mapping to the list using the data from the SAVOIR message
                    # and the new session ID acquired from the session create request.
                    self.mappingList.addMapping(savoir_sessionID, savoirUser, device_sessionID)
                else:
                    # Prevent another session from connecting if there's already one in place.
                    if self.mappingList.getMappingCount():
                        print "Rejecting an authentication request as the device is already being used in a session."
                        return
                    
                    self.device.authenticate(authParameters)
                    
                    # Save the state of the login so that we can send an authentication reply.
                    loginSuccess = "true"
                    
                    # If debugging is enabled, write the event and its data to the terminal.
                    if BI_DEBUG:
                        print 'Device authentication succeeded!'
                        print('Adding (%s, %s, %s) to the shared mapping list.' % (savoirUser, savoir_sessionID, SERVICE_ID))
                    
                    # Add a new mapping to the list using the data from the SAVOIR message
                    # and the service ID tag (since this isn't a multi-session device).
                    self.mappingList.addMapping(savoir_sessionID, savoirUser, SERVICE_ID)
            except OperationFailed:
                # Print an error and do NOT send an acknowledgement, as we're not authenticated.
                print "Device authentication failed."
                return
            except OperationNotSupported as reason:
                # Print the reason why this isn't implemented,
                # and continue with the acknowledgement.
                print reason
            
            # Notify SAVOIR of the authentication's success.
            try:
                self.savoir.acknowledge(savoir_sessionID, savoirUser, 'authenticate')
                if BI_DEBUG:
                    print "Authentication acknowledgement sent successfully."
            except HTTPError:
                print "Authentication acknowledgement failed to be sent to SAVOIR."
        
        elif action == 'load':
            
            loadParameters = self.messageReader.getParameters()
            labyrinth = loadParameters.get('labyrinth').get('value')
            
            try:
                launchUrl = self.device.load(
                    loadParameters = loadParameters,
                    sessionID = device_sessionID,
                    labyrinth = labyrinth)
            except OperationFailed:
                print "Launch process failed."
            except OperationNotSupported as reason:
                print reason
            
            # Notify SAVOIR of the load's success.
            try:
                self.savoir.acknowledge(savoir_sessionID, savoirUser, 'load')
                if BI_DEBUG:
                    print "Load acknowledgement sent successfully."
            except HTTPError:
                print "Load acknowledgement failed to be sent to SAVOIR."
                return
                        
            try:
                self.savoir.launch(savoir_sessionID, savoirUser, launchUrl)
                if BI_DEBUG:
                    print "Launch process successful."
            except HTTPError:
                print "Launch message failed to be sent to SAVOIR."
            
        elif action == 'start':
            
            try:
                if self.device.start() and BI_DEBUG:
                    print 'Session "%s" has been started.' % (device_sessionID,)
            except OperationFailed:
                print 'Session "%s" failed to start.' % (device_sessionID,)
            except OperationNotSupported as reason:
                print reason
            
        elif action == 'stop':
            
            try:
                if self.device.stop() and BI_DEBUG:
                    print 'Session "%s" has been stopped.' % (device_sessionID,)
            except OperationFailed:
                print 'Session "%s" failed to stop.' % (device_sessionID,)
            except OperationNotSupported as reason:
                print reason
            
        elif action == 'pause':
            
            try:
                if self.device.pause(device_sessionID) and BI_DEBUG:
                    print 'Session "%s" has been paused.' % (device_sessionID,)
            except OperationFailed:
                print 'Session "%s" failed to pause.' % (device_sessionID,)
            except OperationNotSupported as reason:
                print reason
            
        elif action == 'resume':
            try:
                if self.device.resume(device_sessionID) and BI_DEBUG:
                    print 'Session "%s" has been resumed.' % (device_sessionID,)
            except OperationFailed:
                print 'Session "%s" failed to resume.' % (device_sessionID,)
            except OperationNotSupported as reason:
                print reason
              
        elif action == 'getStatus':
            
            try:
                deviceParameters = self.device.getStatus(device_sessionID)
            except OperationFailed:
                print 'Session "%s" was unable to provide status.' % (device_sessionID,)
                return
            except OperationNotSupported as reason:
                print reason
            
            try:
                self.savoir.sendStatus(savoir_sessionID, savoirUser, deviceParameters)
                if BI_DEBUG:
                    print 'Session "%s" status has been retrieved.' % (device_sessionID,)
            except HTTPError:
                print 'Session "%s" status failed to be sent to SAVOIR.' % (device_sessionID,)
        elif action == 'getProfile':
            
            try:
                if self.device.getProfile() and BI_DEBUG:
                    print 'The device profile has been retrieved.'
            except OperationFailed:
                print 'The device was unable to provide a profile.'
            except OperationNotSupported as reason:
                print reason
            
        elif action == 'setParameter':
            
            newParameters = self.messageReader.getParameters().itervalues()
            
            try:
                if self.device.setParameter(device_sessionID, newParameters) and BI_DEBUG:
                    print 'Session "%s" has been updated.' % (device_sessionID,)
            except OperationFailed:
                print 'Session "%s" failed to update.' % (device_sessionID,)
            except OperationNotSupported as reason:
                print reason
            
        elif action == 'notify':
            
            try:
                message = 'Please move to the mannequin for this part of the activity.'
                if self.device.notify(device_sessionID, message) and BI_DEBUG:
                    print 'Session "%s" has been notified.' % (device_sessionID,)
            except OperationFailed:
                print 'Session "%s" failed to be notified.' % (device_sessionID,)
            except OperationNotSupported as reason:
                print reason
            
        elif action == 'endSession':
            
            try:
                if self.device.endSession(device_sessionID):
                    # Remove the entry from the shared mapping list
                    self.mappingList.deleteMappingBySavoirData(savoir_sessionID, savoirUser)
                    if BI_DEBUG:
                        print 'Session "%s" has been ended.' % (device_sessionID,)
            except OperationFailed:
                print 'Session "%s" failed to end.' % (device_sessionID,)
            except OperationNotSupported as reason:
                print reason
            
        elif action == 'sessionEnded':
            
            try:
                if self.device.sessionEnded() and BI_DEBUG:
                    print 'Session "%s" has been ended.' % (device_sessionID,)
            except OperationFailed:
                print 'Session "%s" failed to.' % (device_sessionID,)
            except OperationNotSupported as reason:
                print reason